/**************************************************************************(C)*
 *
 *  This file is part of the GreenHills project
 *  Copyright (C) 2009  Tieto Corporation
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  Not for commmercial use. Only with Tieto permission.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************(C)*/

#include "../client.h"

#include "util/binstream.h"
#include "util/memstreambuf.h"

#include "util/conversion.h"

#include "client/core/proxy.h"
#include "client/core/room.h"

#include "core/moveableroomitem.h"

#include <QtCore/QTimer>

#define DEFAULT_UPDATE_PERIOD_SEC 5

using namespace greenhills::client::core;
using namespace greenhills::util;
using namespace greenhills::gps;

CClient::CClient() :
    QObject(0), mRoom(0), mUserName(""), mClientId(0), mLocalUser(0)
{
    CProxy::instance()->registerListener(this);

    QTimer *submitPositionTimer = new QTimer(this);
    submitPositionTimer->setInterval(DEFAULT_UPDATE_PERIOD_SEC * 1000);
    QObject::connect(submitPositionTimer, SIGNAL(timeout()),
                     this, SLOT(onSubmitPositionTimerFired()));
    submitPositionTimer->start();
}


CClient::~CClient()
{
    CProxy::instance()->unregisterListener(this);

    if (isLoggedIn())
        logout();

    if (CProxy::instance()->isConnected())
        CProxy::instance()->disconnect();
}

bool CClient::connect(const QString &pHost)
{
    return CProxy::instance()->connect(qstr2std(pHost));
}

bool CClient::disconnect()
{
    CProxy::instance()->disconnect();
    return true;
}

bool CClient::login(const QString &pUserName, const QString &pPassword)
{
    mUserName = qstr2std(pUserName);
    mClientId = CProxy::instance()->login(mUserName, qstr2std(pPassword));
    Q_EMIT(userLoggedIn());
    return true;
}

bool CClient::logout()
{
    if (!CProxy::instance()->logout(mClientId))
        return false;

    mClientId = 0;
    Q_EMIT(userLoggedOut());
    return true;
}

bool CClient::isLoggedIn() const
{
    return mClientId != 0;
}

bool CClient::isInRoom() const
{
    return mRoom != 0;
}

greenhills::core::CRoomId CClient::createRoom(const QString &pRoomName,
        const greenhills::core::CRoomType &pRoomType, const QString &pPassword)
{
    return CProxy::instance()->createRoom(
            mClientId, qstr2std(pRoomName), pRoomType, qstr2std(pPassword));
}

bool CClient::joinRoom(const greenhills::core::CRoomId &pRoomId,
        const QString &pPassword)
{
    bool succeeded = CProxy::instance()-> joinRoom(
            mClientId, pRoomId, qstr2std(pPassword));

    if (succeeded)
    {
        mRoom = new CRoom(pRoomId, "DEFAULT_ROOM_NAME", this);
        Q_EMIT(roomJoined());
        return true;
    }
    return false;
}

bool CClient::leaveRoom()
{
    if (mRoom == 0)
        return false;

    if (mLocalUser != 0)
    {
        if (!CProxy::instance()->removeItem(
                mClientId, mRoom->id(), mLocalUser->id()))
            return false;
        mLocalUser = 0;
    }

    if (!CProxy::instance()->leaveRoom(mClientId, mRoom->id()))
        return false;

    Q_EMIT(roomLeft());

    delete mRoom;
    mRoom = 0;

    return true;
}

greenhills::core::IRoomItem *CClient::addLocalUser()
{
    greenhills::core::IRoomItem *roomItem =
        addItem(mUserName.c_str(), greenhills::core::CItemType::itUser);

    mLocalUser = dynamic_cast<greenhills::core::CMoveableRoomItem*>(roomItem);
    Q_ASSERT(mLocalUser != 0);

    return roomItem;
}

greenhills::core::IRoomItem *CClient::addItem(const QString &pItemName,
        const greenhills::core::CItemType &pItemType)
{
    Q_ASSERT(mRoom != 0);

    greenhills::core::CItemId itemId = CProxy::instance()->addItem(
        mClientId, mRoom->id(), qstr2std(pItemName), pItemType);

    if (itemId == 0)
        return false;

    mRoom->addItem(itemId, qstr2std(pItemName), pItemType);
    return mRoom->item(itemId);
}


bool CClient::removeItem(greenhills::core::IRoomItem *pRoomItem)
{
    Q_ASSERT(mRoom != 0);
    Q_ASSERT(pRoomItem != 0);

    if (!CProxy::instance()->removeItem(mClientId,
            mRoom->id(), pRoomItem->id()))
        return false;

    return true;
}

bool CClient::updateItem(greenhills::core::IRoomItem *pRoomItem)
{
    Q_ASSERT(mRoom != 0);
    Q_ASSERT(pRoomItem != 0);
    memstreambuf buffer;
    obinstream os(&buffer);
    pRoomItem->operator <<(os);

    if (!CProxy::instance()->updateItem(mClientId, mRoom->id(),
            pRoomItem->id(), (const unsigned char*)buffer.data(),
            buffer.length()))
        return false;

    return true;
}


void CClient::itemAdded(const greenhills::core::CRoomId &pRoomId,
        const greenhills::core::CItemId &pItemId, const std::string &pItemName,
        const greenhills::core::CItemType &pItemType)
{
    std::cout << "CClient: Item Added Event (RoomId:" << pRoomId <<
        ", ItemId:" << pItemId << ")" << std::endl;

    if (mRoom == 0)
        return;

    if (mRoom->id() == pRoomId)
    {
        if (mRoom->item(pItemId) != 0)
        {
            std::cout << "   Item Already Exists !!!" << std::endl;
        }
        else
            mRoom->addItem(pItemId, pItemName, pItemType);
    }
}

void CClient::itemUpdated(const greenhills::core::CRoomId &pRoomId,
        const greenhills::core::CItemId &pItemId, const unsigned char *pData,
        unsigned int pLength)
{
    std::cout << "CClient: Item Updated Event (RoomId:" << pRoomId <<
        ", ItemId:" << pItemId << ")" << std::endl;

    if (mRoom == 0)
        return;

    if (mRoom->id() == pRoomId)
    {
        greenhills::core::IRoomItem *item = mRoom->item(pItemId);
        if ( item == 0)
        {
            std::cout << "   Item Doesn't Exist !!!" << std::endl;
        }
        else
            mRoom->updateItem(pItemId, pData, pLength);
    }
}

void CClient::itemRemoved(const greenhills::core::CRoomId &pRoomId,
        const greenhills::core::CItemId &pItemId)
{
    std::cout << "CClient: Item Removed Event (RoomId:" << pRoomId <<
        ", ItemId:" << pItemId << ")" << std::endl;

    if (mRoom == 0)
        return;

    if (mRoom->id() == pRoomId)
    {
        if (mLocalUser->id() == pItemId)
        {
            //! \todo do something when local user is removed!!!
        }

        greenhills::core::IRoomItem *item = mRoom->item(pItemId);
        if (item == 0)
        {
            std::cout << "   Item Doesn't Exist !!!" << std::endl;
        }
        else
            mRoom->removeItem(pItemId);
    }
}

void CClient::onGPGGAreceived(const QTime &pTime,
    const CGPSPosition &pPosition, const CGPSFix &pFix, int pSatelites,
    float pHdop, float pAltitude, float pGeoidHeight, float pDgpsUpdateDelay,
    int pDgpsStationId)
{
    Q_UNUSED(pGeoidHeight);
    Q_UNUSED(pDgpsUpdateDelay);
    Q_UNUSED(pDgpsStationId);
    Q_UNUSED(pSatelites);
    Q_UNUSED(pHdop);

    if (mLocalUser == 0)
        return;

    if (pFix.isValid())
    {
        mLocalUser->setTimeStamp(util::qtime2sec(pTime));
        mLocalUser->setPosition(pPosition);
        mLocalUser->setAltitude((int)pAltitude);
    }
}

void CClient::onGPVTGreceived(float pHeadingTrueNorth,
    float pHeadingMagnetic, float pSpeedInKnots, float pSpeedInKilometers)
{
    Q_UNUSED(pHeadingTrueNorth);
    Q_UNUSED(pSpeedInKnots);

    if (mLocalUser == 0)
        return;

    mLocalUser->setMovementSpeed(pSpeedInKilometers);
    mLocalUser->setMovementDirection(pHeadingMagnetic);
}

void CClient::onSubmitPositionTimerFired()
{
    if (mLocalUser != 0 && isInRoom() && isLoggedIn())
        updateItem(mLocalUser);
}

